/**
 * ParetoDistribution : Definition de la loi de Pareto
 *
 * @author maria ladoue
 * @version 0.1, 06/08/02 
 */
package br.unb.statistic.distribution;

import java.io.IOException;
import java.util.HashMap;


import br.unb.statistic.InterPacketTimeGenerator;
import br.unb.statistic.RandomGenerator;
import br.unb.statistic.inference.Inference;
import br.unb.statistic.inference.dataReader.DataReader;
import br.unb.utils.Resource;

public class ParetoDistribution extends GenericContinuousDistribution implements RandomGenerator, InterPacketTimeGenerator {

    /**
     * Equation de la densite:	
     *  f(x)= beta*lambda^beta*x^(-(beta+1)) pour x>=lambda, lambda>0, beta>0
     */
    private static int LAMBDA = 0, BETA = 1; // Definition de la "place" des parametres dans le tableau params[]

    /**
     * Definition des constructeurs
     */
    public ParetoDistribution() {
        paramsCount = 2;
        name = new String("Pareto");
        params = new double[paramsCount]; // initialisation du tableau des valeurs des parametres
        paramNames = new String[paramsCount]; // initialisation du tableau des noms des parametres
        params[LAMBDA] = 1.0d;
        params[BETA] = 0.5d;
        paramNames[LAMBDA] = new String("lambda");
        paramNames[BETA] = new String("beta");
    }

    /**
     * construtor
     * média = (beta*lambda)/(beta-1)
     *
     * @param unLambda
     * @param unBeta
     * @throws IllegalArgumentException
     */
    public ParetoDistribution(double unLambda, double unBeta) throws IllegalArgumentException {
        this();
        if (unLambda > 0 && unBeta > 0) {
            setParam(LAMBDA, unLambda);
            setParam(BETA, unBeta);
        } else {
            throw new IllegalArgumentException(
                    Resource.getString("LAMBDA_OR_BETA_LESS_THEN_ZERO"));
        }
    }

    public ParetoDistribution(double[] desParams) throws IllegalArgumentException {
        this();
        if (hasValidParam(desParams)) {
            setParams(desParams);
        } else {
            throw new IllegalArgumentException(
                    Resource.getString("PARETODISTRIBUTION.CONSTRUCTOR_LAMBDA_<_0_||_BETA_<_0"));
        }
    }

    public boolean hasValidParam(double[] desParams) {
        if (desParams[LAMBDA] > 0 && desParams[BETA] > 0) {
            return true;
        } else {
            return false;
        }
    }

    // Definitions de quelques methodes
    /**
     * Returns the value of the probability distribution function at x
     */
    public double pdf(double x) {
        if (x >= params[LAMBDA]) {
            return (params[BETA]
                    * Math.pow(params[LAMBDA], params[BETA])
                    * Math.pow(x, -1d * (params[BETA] + 1))); // to perform the division
        } else {
            return 0.0d;
        }
    }

    /**
     * Returns the value of the cumulative distribution function at x
     */
    public double cdf(double x) {
        if (x > params[LAMBDA]) {
            return (1 - Math.pow(params[LAMBDA] / x, params[BETA]));
        } else {
            return 0.0d;
        }
    }

    /**
     * Return the value of Inverse Function at x
     * @param x with x>=0 and x<=1
     */
    public double inverseCdf(double x) {
        if (x >= 0 && x <= 1) {
            return (params[LAMBDA] * Math.pow(1d - x, -1d / params[BETA]));
        } else {
            return 0d;
        }
    }

    public double nextValue(double x) {
        return this.inverseCdf(x);
    }

    /**
     * Returns the mean of the distribution
     */
    public double mean() {
        if (params[BETA] > 1) {
            return (params[LAMBDA] * params[BETA] / (params[BETA] - 1d));
        } else {
            return Double.POSITIVE_INFINITY;
        }

    }

    /**
     * Returns the variance of the distribution
     */
    public double var() {
        if (params[BETA] > 2) {
            return (Math.pow(params[LAMBDA], 2.0d) * params[BETA]
                    / ((params[BETA] - 2) * Math.pow(params[BETA] - 1, 2.0d)));
        } else {
            return Double.POSITIVE_INFINITY;
        }
    }

    //  public double rand() {
    //	return (1/params[LAMBDA] * Math.pow( 1.0d - random.nextDouble() , -1.0d/params[BETA] ));
    // }
    public double rand() {
        return this.inverseCdf(random.nextDouble());
    }

    public double nextInterPacketTime() {
        return rand();
    }

    public String getEquation() {
        return new String("f(x)= beta*lambda^beta*x^(-(beta+1)) with x>=lambda, lambda>0, beta>0");
    }

    public String xml() {
        String xml = "<PARETO>"
                + "<MEAN>" + this.mean() + "</MEAN>"
                + "<ALFA>" + params[BETA] + "</ALFA>"
                + "</PARETO>";
        return xml;
    }

    public void setParameters(HashMap parameters) {
        double mean;
        if (parameters.get("mean") != null) {
            mean = Double.parseDouble((String) parameters.get("mean"));
        } else {
            mean = 1.0d / Double.parseDouble((String) parameters.get("rate"));
        }
        double alfa = java.lang.Double.parseDouble((String) parameters.get("alfa"));
        setParam(LAMBDA, ((alfa - 1) * mean) / alfa);
        setParam(BETA, alfa);
    }

    public void estimateParameters(Inference inf) {
        double lambda, beta = 0, valor;
        long n;
        lambda = inf.getMin();
        if (lambda > 0) {
            n = inf.getSampleSize();
            double aux = Math.log(lambda);
            try {
                String nextString;
                inf.rewind();
                while ((nextString = inf.nextValue()) != null) {
                    try {
                        valor = Double.parseDouble(nextString);
                        beta += Math.log(valor) - aux;
                    } catch (NumberFormatException e) {
                    }
                }
                beta = n / beta;
                setParam(LAMBDA, lambda);
                setParam(BETA, beta);
            } catch (IOException e) {
                e.printStackTrace();
            }
        } else {
            throw new IllegalArgumentException(
                    Resource.getString("PARETO_PARAMETER_INCORRECT"));
        }
    }

    /* //Old code with the testing unit and stuff YO!!!
    public static void main(String args[]) {
    ParetoDistribution dist = new ParetoDistribution(0.0712, 3.5);
    ParetoDistribution dist2 = new ParetoDistribution();
    Log log = new Log("Pareto_teste", false);
    for (int i = 0; i < 3000; i++) {
    log.write("" + dist.nextInterPacketTime());
    }
    log.close();
    try {
    DataReader dr = new DataReader(log.getFile());
    Inference inf = new Inference(dr, 0);
    dist2.estimateParameters(inf);
    System.out.println(dist2.getParam(0) + " " + dist2.getParam(1));
    } catch (IOException e) {
    e.printStackTrace();
    }
    }*/
}
